package org.belkodevelop.photo.service;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.belkodevelop.photo.dao.PhotoDAO;
import org.belkodevelop.photo.domen.Photo;
import org.belkodevelop.realty.service.RealtyService;
import org.belkodevelop.user.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

/**
 * @author VMozolev
 */
@Service
public class PhotoService {

    private final Log log = LogFactory.getLog(getClass());

    @Value("${web.photo.upload.max_width}")
    private int maxWidth;

    @Value("${web.photo.upload.max_height}")
    private int maxHeight;

    @Value("${web.photo.path}")
    private String imagePath;

    @Value("${web.photo.upload.quality}")
    private Float photoQuality;

    @Autowired
    private PhotoDAO photoDAO;

    @Autowired
    private RealtyService realtyService;

    private BufferedImage getScaledImage(BufferedImage image, int width, int height) throws IOException {
        int imageWidth  = image.getWidth();
        int imageHeight = image.getHeight();

        double scaleX = (double)width/imageWidth;
        double scaleY = (double)height/imageHeight;

        double scale;
        if (scaleX < scaleY) {
            scale = scaleX;
        } else {
            scale = scaleY;
        }

        AffineTransform scaleTransform = AffineTransform.getScaleInstance(scale, scale);
        AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);

        return bilinearScaleOp.filter(image, new BufferedImage((int)(imageWidth * scale), (int)(imageHeight * scale), image.getType()));
    }

    private void saveImage(MultipartFile file, File photoFile) throws IOException {
        ImageOutputStream ios =  ImageIO.createImageOutputStream(photoFile);
        Iterator<ImageWriter> imageWriterIterator = ImageIO.getImageWritersByFormatName("jpeg");
        ImageWriter writer = imageWriterIterator.next();
        ImageWriteParam iwp = writer.getDefaultWriteParam();
        iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        iwp.setCompressionQuality(photoQuality);
        writer.setOutput(ios);
        writer.write(null, new IIOImage(getScaledImage(ImageIO.read(file.getInputStream()), maxWidth, maxHeight),null,null),iwp);
        writer.dispose();
    }

    @Transactional
    public Photo addPhoto(MultipartFile file, long realtyId, String description) {
        try {
            String uniqFileName = UUID.nameUUIDFromBytes(file.getBytes()) + FilenameUtils.getExtension(file.getName()) + ".jpg";

            String fullUniqFileName = imagePath + uniqFileName;
            File photoFile = new File(fullUniqFileName);
            if(!photoFile.exists()) {
                saveImage(file, photoFile);
            }
            Photo photo = new Photo();
            photo.setFileName(uniqFileName);
            photo.setRealty(realtyService.getRealtyById(realtyId));
            photo.setDescription(description);
            photoDAO.addPhoto(photo);

            return photo;
        } catch (Exception ex) {
            log.warn(ex);
            throw new RuntimeException(ex);
        }
    }

    @Transactional(readOnly = true)
    public List<Photo> getPhotos(long realtyId) {
        return photoDAO.getPhotos(realtyId);
    }

    @Transactional
    public boolean deletePhoto(long photoId, User owner) {
        if (photoDAO.userHavePhoto(owner.getId(), photoId)) {
            return photoDAO.deletePhoto(photoId);
        }
        return false;
    }

    @Transactional
    public boolean setMainPhoto(Long photoId, long realtyId, User owner) {
        if (photoDAO.userHavePhoto(owner.getId(), realtyId, photoId)) {
            return photoDAO.setMainPhoto(realtyId, photoId);
        }
        return false;
    }
}
